/*
 * Copyright (c) 2020-2099 the original author or authors. All rights reserve.
 */
package org.tianyun.cloud.mybatis.plugins.sqlparser;

import org.springframework.aop.framework.Advised;
import org.springframework.aop.support.AopUtils;
import org.springframework.cloud.util.ProxyUtils;
import org.tianyun.cloud.datasource.WrappedDataSource;
import org.tianyun.cloud.datasource.routing.RoutingDataSource;
import org.tianyun.cloud.mybatis.plugins.sqlparser.dialect.Db2DialectParser;
import org.tianyun.cloud.mybatis.plugins.sqlparser.dialect.HsqldbDialectParser;
import org.tianyun.cloud.mybatis.plugins.sqlparser.dialect.MysqlDialectParser;
import org.tianyun.cloud.mybatis.plugins.sqlparser.dialect.OracleDialectParser;
import org.tianyun.cloud.mybatis.plugins.sqlparser.dialect.SqlServer2012DialectParser;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * SqlParserFactory
 *
 * @auther ebert_chan
 */
public class SqlParserFactory {

    private static final Map<String, Class<? extends SqlParser>> DIALECT_ALIAS = new HashMap<String, Class<? extends SqlParser>>();

    private static final Map<DataSource, SqlParser> CACHE = new ConcurrentHashMap<>();

    private static final SqlParserFactory FACTORY = new SqlParserFactory();

    private static void registerDialectAlias(String alias, Class<? extends SqlParser> dialectClass) {
        DIALECT_ALIAS.put(alias, dialectClass);
    }

    static {
        // 注册别名
        registerDialectAlias("mysql", MysqlDialectParser.class);
        registerDialectAlias("mariadb", MysqlDialectParser.class);
        registerDialectAlias("sqlite", MysqlDialectParser.class);

        registerDialectAlias("oracle", OracleDialectParser.class);
        registerDialectAlias("db2", Db2DialectParser.class);
        registerDialectAlias("sqlserver2012", SqlServer2012DialectParser.class);

        registerDialectAlias("derby", SqlServer2012DialectParser.class);
        // 达梦数据库
        registerDialectAlias("dm", OracleDialectParser.class);
        // 阿里云PPAS数据库
        registerDialectAlias("edb", OracleDialectParser.class);

        registerDialectAlias("hsqldb", HsqldbDialectParser.class);
        registerDialectAlias("h2", HsqldbDialectParser.class);
        registerDialectAlias("postgresql", HsqldbDialectParser.class);
        registerDialectAlias("phoenix", HsqldbDialectParser.class);
    }

    public static SqlParser resolveDataSource(DataSource dataSource) {
        String jdbcUrl = FACTORY.getJdbcUrl(dataSource);
        if (jdbcUrl == null) {
            return null;
        }
        String dialect = FACTORY.fromJdbcUrl(jdbcUrl);
        if (dialect == null) {
            return null;
        }

        SqlParser sqlParser = CACHE.get(dialect);
        if (sqlParser == null) {
            sqlParser = FACTORY.resolveSqlParser(dialect);
            CACHE.put(dataSource, sqlParser);
        }
        return sqlParser;
    }

    private SqlParser resolveSqlParser(String dialect) {
        Class<?> clazz = DIALECT_ALIAS.get(dialect);
        if (clazz == null) {
            return null;
        }

        SqlParser sqlParser = null;
        try {
            sqlParser = (SqlParser) clazz.newInstance();
        } catch (InstantiationException e) {
            // ignore
        } catch (IllegalAccessException e) {
            // ignore
        }
        return sqlParser;
    }

    private String fromJdbcUrl(String jdbcUrl) {
        for (String dialect : DIALECT_ALIAS.keySet()) {
            if (jdbcUrl.indexOf(":" + dialect + ":") != -1) {
                return dialect;
            }
        }
        return null;
    }

    private String getJdbcUrl(DataSource dataSource) {
        DataSource target = dataSource;

        if (AopUtils.isAopProxy(dataSource) && (dataSource instanceof Advised)) {
            target = ProxyUtils.getTargetObject(target);
        }

        if (target instanceof RoutingDataSource) {
            RoutingDataSource rds = (RoutingDataSource) target;
            try {
                target = rds.resolveDataSource();
            } catch (SQLException e) {
            }
        }

        if (target instanceof WrappedDataSource) {
            return ((WrappedDataSource) target).getJdbcUrl();
        }

        Connection conn = null;
        try {
            conn = dataSource.getConnection();
            return conn.getMetaData().getURL();
        } catch (SQLException e) {
        } finally {
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    // ignore
                }
            }
        }
        return null;
    }

}
